函数参数注解和作用, 以及参数注解在业务中的应用, inspect模块的使用

函数参数注解和作用, 以及参数注解在业务中的应用, inspect模块的使用

函数参数注解

函数定义的弊端:

  • Python是动态语言,变量随时可以被赋值,且能被赋值为不同的类型
  • Python不是静态编译型语言,变量的类型实在运行期间决定的
  • 动态语言很灵活,但是这种特性也是有弊端的
    举例:
1
2
3
4
5
6
7
def add(x,y):
return x + y

print(add(4,5))
print(add('a','b'))

print(add(4,'a')) # TypeError: unsupported operand type(s) for +: 'int' and 'str'

问题:

  1. 难发现:由于不做任何的类型检查,直至运行期问题才显现出来,或者线上运行时才能暴露出现的问题
  2. 难使用:函数的使用者看到函数类型时,并不知道你的函数的设计,所以也不知道应该传入什么类型的数据

弊端的解决途径

文档注释
  • 增加文档 Documentation String
  • 这不是一个管理,不是强制标准,但是,建议程序员一定要为函数提供说明文档
  • 但是 函数定义更新后,函数文档未必同步更新
1
2
3
4
5
6
7
8
9
10
def add(x,y):
"""

:param x:
:param y:
:return:
"""
return x + y

print(help(add))
函数注解 Annotations

如何解决这种动态语言定义的弊端呢?
通常使用的就是函数注解 Annotations

1
2
3
4
5
6
7
8
9
10
11
12
def add(x:int,y:int) ->int:  # 函数参数、函数返回值进行注解
"""

:param x:
:param y:
:return:
"""
return x + y

print(help((add)))
print('*'*40)
print(dir(add))
Help on function add in module __main__:

add(x: int, y: int) -> int
    :param x:
    :param y:
    :return:

None
****************************************
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
函数注解的属性保留

由以上代码可知,函数注解都是保存在函数annotations中,函数注解到底是怎么回事呢?

  • Python3,5引入函数注解功能
  • 对函数的参数进行类型注解
  • 对函数的返回值进行类型注解
  • 只对函数参数做一个辅助的说明,并不对函数参数进行类型检查
  • 可以提供给第三发工具,做代码分析,发现隐藏的bug
  • 函数注解的信息,保存在annotations属性中
  • 根据函数注解的形式,即可知道存储在字典中的
1
2
3
4
5
6
7
8
def add(x:int,y:int) ->int:
"""

:param x:
:param y:
:return:
"""
print(add.__annotations__)
{'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}

函数参数类型的检查应用

函数参数类型检查思路:

  • 函数参数的检查,一定是函数外部检查
  • 函数应该作为参数,传入到检查函数中 [装饰器]
  • 检查函数拿到函数传入的实际参数,与形参什么对比
  • annotations属性是一个字典,其中包含返回值类型是声明,假设要做位置参数的判断,就无法与字典的声明对应,使用inspect模块
  • inspect模块提供获取对象信息的函数,可以做到对 函数和类的的类型检测

inspect模块的参数检查功能

在使用inspect模块检查参数时,首先要对函数签名、签名的参数、参数中的元素要有清晰的认知,要从架构上把控他们之间的管理

  • 签名对象:
    签名: 包含函数信息,如函数名、函数参数类型、命名空间等常用属性
    例如:‘bind’,‘bind partial’,‘empty’,‘parameters’,‘replace’,‘return_annotation’
  • 参数对象:
    签名属性的参数:包含参数属性,有序字典OrderedDict
    例如:‘items’,‘keys’,‘values’
  • 元素对象:
    签名属性的参数中的元素:有序字典中的value的常用属性
    例如:‘annotation’,‘default’,‘empty’,‘kind’,‘name’,‘replace’
    如果元素没有注释或者没有default默认值时,该属性就为empty

元素对象属性为字典值的属性

案例:

  • 先查看位置参数的注释,必须通过字典中key的值得到注释类型
  • 是字典值表现形式\<\Parameter “x:int”>,要进步通过查看他的\<\Parameter “x:int”>.annotation才能得到 class int整正的数据类型
  • 在参数核验时,如isinstance(3,int) 中int 就是class int 只有这样才能对比,而\<\Parameter “x:int”>在正常情况下是无法使用的,必须通过annotation将其类型解放出来才能使用
    举例:
1
2
3
4
5
6
7
8
9
10
import inspect
def add(x:int,y:int=6,*args,**kwargs)->int:
return x + y

sig = inspect.signature(add)
params = sig.parameters
values = [ x for x in params.values() ]
print(values)
for i,v in enumerate(values):
print(values[i].annotation)
[<Parameter "x: int">, <Parameter "y: int = 6">, <Parameter "*args">, <Parameter "**kwargs">]
<class 'int'>
<class 'int'>
<class 'inspect._empty'>
<class 'inspect._empty'>

signature获取签名

inspect.signature(callable),获取签名(函数签名中包含了一个函数信息,包括函数名、函数参数、函数所在的类和命名空间及其他信息)
举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import inspect
def add(x:int,y:int=6,*args,**kwargs)->int:
return x + y

sig = inspect.signature(add)
params = sig.parameters
print(sig)
print(params)
print(sig.return_annotation)
print('-'*40)
print(params['y'])
print(params['y'].annotation)
print(params['y'].default)
print('='*40)
print(params['x'])
print(params['x'].annotation)
print(params['x'].default)
print('-'*40)
print(params['args'])
print(params['args'].annotation)
print(params['args'].default)
print('='*40)
print(params['kwargs'])
print(params['kwargs'].annotation)
print(params['kwargs'].default)
(x: int, y: int = 6, *args, **kwargs) -> int
OrderedDict([('x', <Parameter "x: int">), ('y', <Parameter "y: int = 6">), ('args', <Parameter "*args">), ('kwargs', <Parameter "**kwargs">)])
<class 'int'>
----------------------------------------
y: int = 6
<class 'int'>
6
========================================
x: int
<class 'int'>
<class 'inspect._empty'>
----------------------------------------
*args
<class 'inspect._empty'>
<class 'inspect._empty'>
========================================
**kwargs
<class 'inspect._empty'>
<class 'inspect._empty'>

对象的判断

  • inspect.isfunction(add) 是否是函数
  • inspect.ismethod(add) 是否是类的方法
  • inspect.isgenerator(add) 是否是生成器对象
  • inspect.isgenerafunction(add) 是否是生成器函数
  • inspect.isclass(add) 是否是类
  • inspect.ismodule(inspect) 是否是模块
  • inspect.isbuiltin(print) 是否是内建对象

parameter参数对象

  • 保存在元组中,是只读属性
  • name,参数的名字
  • annotation,参数的注解,可以不定义
  • default,参数的缺省值,可以不定义
  • empty,特殊的类,用来标识default 或者注释annotation属性为空
  • kind,实参如何绑定到形参,就是形参的类型
    @ POSITIONAL_ONLY 值必须由位置参数提供
    @ POSITIONAL_KEYWORD 值可以作为关键字或者位置参数提供
    @ VAR_POSITIONAL 可变位置参数,相当于 args
    @ KEYWORD_ONLY ,keyword-only参数,相当于
    或者 args 后出现的非可变关键参数
    @ VAR_KEYWORD, 可变关键参数,相当于
    * kwargs

案例操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import inspect
def add(x, y:int=7, *args, z, t = 10, **kwargs)->int:
return x + y

sig = inspect.signature(add)
params = sig.parameters

print(params)

for i,v in enumerate(params.items()): # 通过迭代有序字典的方式获取value
name,param = v
print()
print(i+1,name,param.annotation,param.kind,param.default)

print(param.default,param.empty)
print('*' * 50)
OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y: int = 7">), ('args', <Parameter "*args">), ('z', <Parameter "z">), ('t', <Parameter "t=10">), ('kwargs', <Parameter "**kwargs">)])

1 x <class 'inspect._empty'> POSITIONAL_OR_KEYWORD <class 'inspect._empty'>
<class 'inspect._empty'> <class 'inspect._empty'>
**************************************************

2 y <class 'int'> POSITIONAL_OR_KEYWORD 7
7 <class 'inspect._empty'>
**************************************************

3 args <class 'inspect._empty'> VAR_POSITIONAL <class 'inspect._empty'>
<class 'inspect._empty'> <class 'inspect._empty'>
**************************************************

4 z <class 'inspect._empty'> KEYWORD_ONLY <class 'inspect._empty'>
<class 'inspect._empty'> <class 'inspect._empty'>
**************************************************

5 t <class 'inspect._empty'> KEYWORD_ONLY 10
10 <class 'inspect._empty'>
**************************************************

6 kwargs <class 'inspect._empty'> VAR_KEYWORD <class 'inspect._empty'>
<class 'inspect._empty'> <class 'inspect._empty'>
**************************************************

传参检查

需求: 请检查用户输入参数是否符合参数注解的要求

1
2
def add(x,y:int=7)->:
return x + y

解题思路:
可以通过ordereddict.annotation获取参数的注解类型,再通过isinstance(值,类型)对比

解决方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import inspect
def check(fn):
def wapper(*args,**kwargs):
sig = inspect.signature(fn)
params = sig.parameters
value = list(params.values()) # 字典的values,为的是通过value.annotation 解放注释类型
print(values)
for i ,p in enumerate(args):
param = values[i]
if param.annotation is not param.empty and isinstance(p,values[i].annotation):
print('OK')

for k,v in kwargs.items(): # 获取数据类型第二种手段,直接通过 dict[key].annotation 等价于 values.annotation
if params[k].annotation is not params[k].empty and isinstance(v,params[k].annotation):
print('OK')

func = fn(*args,**kwargs)
return func
return wapper

@check # add = check(add)
def add(x,y:int=7)->int:
return x + y

print(add(3,y=4))
[<Parameter "x: int">, <Parameter "y: int = 6">, <Parameter "*args">, <Parameter "**kwargs">]
OK
OK
7

总结:
获取具体某参数的注释:
方法1. 通过字典值获取参数类型

1
2
3
Values = list(inpect,signature.paramters)
For v in Values:
print(v.annotation)

方法2:通过字典key获取参数类型

1
print(dict[k].annotation)

-------------本文结束感谢您的阅读-------------
0%